////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                                                            //
//                                                         WNProject                                                          //
//                                                                                                                            //
//            This file is distributed under the BSD 2-Clause open source license. See License.txt for details.               //
//                                                                                                                            //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#ifndef __WN_MUTEX_INL__
#define __WN_MUTEX_INL__

#include "WNCore/inc/WNBase.h"
#include "WNCore/inc/WNAssert.h"

#ifdef _WN_WINDOWS
    #include <windows.h>
#elif defined _WN_LINUX
    #include <pthread.h>
#endif

__WN_INLINE WN_VOID WNMutexInitialize(WN_MUTEX_T* _mutex) {
    #ifdef _WN_LINUX
        WN_UINT32 result;
    #endif

    WN_RELEASE_ASSERT(_mutex != WN_NULL);

    #ifdef _WN_WINDOWS
        *(_mutex) = CreateMutex(WN_NULL, 0, WN_NULL);

        WN_RELEASE_ASSERT(*(_mutex) != WN_NULL);
    #elif defined _WN_LINUX
        pthread_mutexattr_t attributes;

        WN_RELEASE_ASSERT((result = pthread_mutexattr_init(&attributes)) == 0);

        if (result == 0) {
            WN_RELEASE_ASSERT((result = pthread_mutexattr_settype(&attributes, PTHREAD_MUTEX_NORMAL)) == 0);

            if (result == 0) {
                WN_RELEASE_ASSERT((result = pthread_mutexattr_setrobust(&attributes, PTHREAD_MUTEX_ROBUST)) == 0);

                if (result == 0) {
                    WN_RELEASE_ASSERT((result = pthread_mutex_init(_mutex, &attributes)) == 0);

                    if (result == 0) {
                        WN_RELEASE_ASSERT(pthread_mutexattr_destroy(&attributes) == 0);
                    }
                }
            }
        }
    #endif
}

__WN_INLINE WN_VOID WNMutexDestroy(WN_MUTEX_T* _mutex) {
    WN_RELEASE_ASSERT(_mutex != WN_NULL);

    #ifdef _WN_WINDOWS
        WN_RELEASE_ASSERT(CloseHandle(*(_mutex)) != 0);
    #elif defined _WN_LINUX
        WN_RELEASE_ASSERT(pthread_mutex_destroy(_mutex) == 0);
    #endif
}

__WN_INLINE WN_VOID WNMutexLock(WN_MUTEX_T* _mutex) {
    WN_RELEASE_ASSERT(_mutex != WN_NULL);

    #ifdef _WN_WINDOWS
        WN_RELEASE_ASSERT(WaitForSingleObject(*(_mutex), INFINITE) != WAIT_OBJECT_0);
    #elif defined _WN_LINUX
        WN_RELEASE_ASSERT(pthread_mutex_lock(_mutex) == 0);
    #endif
}

__WN_INLINE WN_VOID WNMutexUnlock(WN_MUTEX_T* _mutex) {
    WN_RELEASE_ASSERT(_mutex != WN_NULL);

    #ifdef _WN_WINDOWS
        WN_RELEASE_ASSERT(ReleaseMutex(*(_mutex)) != 0);
    #elif defined _WN_LINUX
        WN_RELEASE_ASSERT(pthread_mutex_unlock(_mutex) == 0);
    #endif
}

#ifdef _WN_CPLUSPLUS
    __WN_INLINE WNMutex::WNMutex() {
        WNMutexInitialize(&mMutex);
    }

    __WN_INLINE WNMutex::~WNMutex() {
        WNMutexDestroy(&mMutex);
    }

    __WN_INLINE WN_VOID WNMutex::Lock() {
        WNMutexLock(&mMutex);
    }

    __WN_INLINE WN_VOID WNMutex::Unlock() {
        WNMutexUnlock(&mMutex);
    }

    __WN_INLINE WNMutexScopeLock::WNMutexScopeLock(WN_MUTEX_T* _mutex) {
        WN_RELEASE_ASSERT(_mutex != WN_NULL);

        mMutex = _mutex;

        WNMutexLock(mMutex);
    }

    __WN_INLINE WNMutexScopeLock::WNMutexScopeLock(WNMutex& _mutex) {
        mMutex = &(_mutex.mMutex);

        WNMutexLock(mMutex);
    }

    __WN_INLINE WNMutexScopeLock::~WNMutexScopeLock() {
        WNMutexUnlock(mMutex);
    }
#endif

#endif // __WN_MUTEX_INL__
